package com.base.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HttpInclude
{
  private static final Log log = LogFactory.getLog(HttpInclude.class);
  public static String sessionIdKey = "JSESSIONID";
  private HttpServletRequest request;
  private HttpServletResponse response;
  private static final String SET_COOKIE_SEPARATOR = "; ";
  
  public HttpInclude(HttpServletRequest request, HttpServletResponse response)
  {
    this.request = request;
    this.response = response;
  }
  
  public String include(String includePath)
  {
    StringWriter sw = new StringWriter(8192);
    include(includePath, sw);
    return sw.toString();
  }
  
  public void include(String includePath, Writer writer)
  {
    try
    {
      if (isRemoteHttpRequest(includePath)) {
        getRemoteContent(includePath, writer);
      } else {
        getLocalContent(includePath, writer);
      }
    }
    catch (ServletException e)
    {
      throw new RuntimeException("include error,path:" + includePath + 
        " cause:" + e, e);
    }
    catch (IOException e)
    {
      throw new RuntimeException("include error,path:" + includePath + 
        " cause:" + e, e);
    }
  }
  
  private static boolean isRemoteHttpRequest(String includePath)
  {
    return (includePath != null) && (
      (includePath.toLowerCase().startsWith("http://")) || 
      (includePath.toLowerCase().startsWith("https://")));
  }
  
  private void getLocalContent(String includePath, Writer writer)
    throws ServletException, IOException
  {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);
    CustomOutputHttpServletResponseWrapper customResponse = new CustomOutputHttpServletResponseWrapper(
      this.response, writer, outputStream);
    String url_path = includePath.indexOf("?") > 0 ? includePath.substring(
      0, includePath.indexOf("?")) : includePath;
    String query = includePath.indexOf("?") > 0 ? includePath
      .substring(includePath.indexOf("?") + 1) : "";
    String[] params = query.split("&");
    for (String param : params) {
      if ((param != null) && (!param.equals("")))
      {
        String[] list = param.split("=");
        if (list.length == 2) {
          this.request.setAttribute(list[0], list[1]);
        }
      }
    }
    this.request.getRequestDispatcher(url_path).include(this.request, customResponse);
    customResponse.flushBuffer();
    if (customResponse.useOutputStream) {
      writer.write(outputStream.toString(this.response
        .getCharacterEncoding()));
    }
    writer.flush();
  }
  
  private void getRemoteContent(String urlString, Writer writer)
    throws MalformedURLException, IOException
  {
    URL url = new URL(getWithSessionIdUrl(urlString));
    URLConnection conn = url.openConnection();
    setConnectionHeaders(urlString, conn);
    InputStream input = conn.getInputStream();
    try
    {
      Reader reader = new InputStreamReader(input, 
        Utils.getContentEncoding(conn, this.response));
      Utils.copy(reader, writer);
    }
    finally
    {
      if (input != null) {
        input.close();
      }
    }
    writer.flush();
  }
  
  private void setConnectionHeaders(String urlString, URLConnection conn)
  {
    conn.setReadTimeout(6000);
    conn.setConnectTimeout(6000);
    String cookie = getCookieString();
    conn.setRequestProperty("Cookie", cookie);
    if (log.isDebugEnabled()) {
      log.debug("request properties:" + conn.getRequestProperties() + 
        " for url:" + urlString);
    }
  }
  
  private String getWithSessionIdUrl(String url)
  {
    return url;
  }
  
  private String getCookieString()
  {
    StringBuffer sb = new StringBuffer(64);
    Cookie[] cookies = this.request.getCookies();
    if (cookies != null) {
      for (Cookie c : cookies) {
        if (!sessionIdKey.equals(c.getName())) {
          sb.append(c.getName()).append("=").append(c.getValue()).append("; ");
        }
      }
    }
    String sessionId = Utils.getSessionId(this.request);
    if (sessionId != null) {
      sb.append(sessionIdKey).append("=").append(sessionId).append(
        "; ");
    }
    return sb.toString();
  }
  
  public static class CustomOutputHttpServletResponseWrapper
    extends HttpServletResponseWrapper
  {
    public boolean useWriter = false;
    public boolean useOutputStream = false;
    private PrintWriter printWriter;
    private ServletOutputStream servletOutputStream;
    
    public CustomOutputHttpServletResponseWrapper(HttpServletResponse response, Writer customWriter, final OutputStream customOutputStream)
	{
		super(response);
		useWriter = false;
		useOutputStream = false;
		printWriter = new PrintWriter(customWriter);
		servletOutputStream = new ServletOutputStream() {

			public void write(int b)
				throws IOException
			{
				customOutputStream.write(b);
			}

			public void write(byte b[])
				throws IOException
			{
				customOutputStream.write(b);
			}

			public void write(byte b[], int off, int len)
				throws IOException
			{
				customOutputStream.write(b, off, len);
			}

			
		};
	}

    
    public PrintWriter getWriter()
      throws IOException
    {
      if (this.useOutputStream) {
        throw new IllegalStateException(
          "getOutputStream() has already been called for this response");
      }
      this.useWriter = true;
      return this.printWriter;
    }
    
    public ServletOutputStream getOutputStream()
      throws IOException
    {
      if (this.useWriter) {
        throw new IllegalStateException(
          "getWriter() has already been called for this response");
      }
      this.useOutputStream = true;
      return this.servletOutputStream;
    }
    
    public void flushBuffer()
      throws IOException
    {
      if (this.useWriter) {
        this.printWriter.flush();
      }
      if (this.useOutputStream) {
        this.servletOutputStream.flush();
      }
    }
  }

  static class Utils
  {
    static String getContentEncoding(URLConnection conn, HttpServletResponse response)
    {
      String contentEncoding = conn.getContentEncoding();
      if (conn.getContentEncoding() == null)
      {
        contentEncoding = parseContentTypeForCharset(conn
          .getContentType());
        if (contentEncoding == null) {
          contentEncoding = response.getCharacterEncoding();
        }
      }
      else
      {
        contentEncoding = conn.getContentEncoding();
      }
      return contentEncoding;
    }
    
    static Pattern p = Pattern.compile("(charset=)(.*)", 
      2);
    
    private static String parseContentTypeForCharset(String contentType)
    {
      if (contentType == null) {
        return null;
      }
      Matcher m = p.matcher(contentType);
      if (m.find()) {
        return m.group(2).trim();
      }
      return null;
    }
    
    private static void copy(Reader in, Writer out)
      throws IOException
    {
      char[] buff = new char[8192];
      while (in.read(buff) >= 0) {
        out.write(buff);
      }
    }
    
    private static String getSessionId(HttpServletRequest request)
    {
      HttpSession session = request.getSession(false);
      if (session == null) {
        return null;
      }
      return session.getId();
    }
  }
}
